package fr.bludwarf.skywarrior.ile;

import static fr.bludwarf.skywarrior.ile.Batiment.ATELIER;
import static fr.bludwarf.skywarrior.ile.Batiment.CHAMPS;
import static fr.bludwarf.skywarrior.ile.Batiment.Carrière;
import static fr.bludwarf.skywarrior.ile.Batiment.TISSERAND;
import static fr.bludwarf.skywarrior.ile.Batiment.École;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.Vector;

import fr.bludwarf.skywarrior.evenements.CommencerConstruction;
import fr.bludwarf.skywarrior.evenements.Evenement;
import fr.bludwarf.skywarrior.evenements.EvenementImpossible;
import fr.bludwarf.skywarrior.evenements.NouvelHabitant;
import fr.bludwarf.skywarrior.evenements.TerminerConstruction;
import fr.bludwarf.skywarrior.exceptions.EtatIncoherentException;
import fr.bludwarf.skywarrior.global.EffetsSurIle;
import fr.bludwarf.skywarrior.global.Etat;
import fr.bludwarf.skywarrior.global.EtatMonde;
import fr.bludwarf.skywarrior.global.FileDAttente;
import fr.bludwarf.skywarrior.global.Temps;
import fr.bludwarf.skywarrior.global.TimeFactorInconnu;
import fr.bludwarf.skywarrior.joueur.EtatJoueur;
import fr.bludwarf.skywarrior.joueur.Ressources;
import fr.bludwarf.skywarrior.joueur.Techno;
import fr.bludwarf.skywarrior.joueur.Unite;
import fr.bludwarf.skywarrior.joueur.Vaisseau;

public class EtatIle extends Etat<Ile> implements EffetsSurIle
{
	
	/** Population initiale sur l'ile principale */
	public static final int POP_INITIALE = 3;

	/** Nombre de bâtiment de chaque type */
	HashMap<Batiment, Integer> _batiments = new HashMap<Batiment, Integer>();
	
	/** File d'attente des construction, les temps restants ne prenne en compte que l'état actuel de l'ile */
	FileDAttente<Construction> _constructions = new FileDAttente<Construction>();
	
	/** Population */
	int _pop;
	
	/** Nourriture */
	int _nourriture;
	
	/** Temps restant avant la prochaine pop */
	double _nextPop;
	
	public EtatIle(final Ile ile, final EtatJoueur etatJoueur) {
		super(ile, etatJoueur);
		
		// Lien ile et joueur OK ?
		if (!etatJoueur.getJoueur().possede(ile))
			throw new EtatIncoherentException(String.format(
				"Le joueur %s doit posséder l'île %s avant de pouvoir créer un nouvel état pour cette île.",
				etatJoueur.getJoueur().getNom(),
				ile.getNom()));
		
		if (ile.ilePrincipale())
		{
			// De base, l'ile principale possède un palais
			_batiments.put(Batiment.PALAIS, 1);
			_pop = POP_INITIALE;
		}
		else
		{
			_pop = 0;
		}
		
		_nourriture = getProduction(true).nourriture();
		_nextPop = getInitialNextPop();
	}

	public EtatIle(EtatIle etatInitial, final EtatJoueur etatJoueur) {
		super(etatInitial, etatJoueur);
		
		// Clone des bâtiments
		for (final Batiment batiment : etatInitial._batiments.keySet())
		{
			final Integer nb = etatInitial._batiments.get(batiment);
			_batiments.put(batiment, nb);
		}
		
		// Clone des constructions
		for (final Construction construction : etatInitial._constructions)
		{
			final Construction newConstruction = new Construction(construction, getIle());
			_constructions.add(newConstruction);
		}
		
		_pop = etatInitial._pop;
		_nourriture = etatInitial._nourriture;
		_nextPop = etatInitial._nextPop;
	}
	
	/**
	 * @return la production totale de matériaux et d'éther de l'île
	 */
	public Ressources getProduction(final boolean cumulNourriture)
	{
		Ressources prod = new Ressources(0, 0, 0);
		
		for (final Batiment batiment : _batiments.keySet())
		{
			prod.add(Ressources.total(getNb(batiment), getProduction(batiment, this)), cumulNourriture);
		}
		
		
		
		return prod;
	}

	/**
	 * @param batiment type du bâtiment recherché
	 * @return le nombre de bâtiments (terminés) du type recherché dans cette île
	 */
	public int getNb(Batiment batiment) {
		if (_batiments.containsKey(batiment))
		{
			return _batiments.get(batiment);
		}
		else
		{
			return 0;
		}
	}

	/**
	 * @param batiment type du bâtiment recherché
	 * @param etatIle 
	 * @return production d'un type de bâtiment à un instant donné sur cette île
	 */
	public Ressources getProduction(Batiment batiment, EtatIle etatIle) {
		return batiment.getProduction(etatIle);
	}
	
	public StatsCombat getStatsCombat()
	{
		final StatsCombat stats = new StatsCombat(0, 1, 1); // de base on a 1 d'att et 1 de def
		for (final Batiment bat : _batiments.keySet())
		{
			final StatsCombat statsBat = bat.getStatsCombat();
			if (statsBat != null)
			{
				final int nb = _batiments.get(bat);
				stats.add(statsBat, nb);
			}
		}
		
		// Appliquer technos
		appliquerEffetsSurIle(stats);
		
		return stats;
	}
	
	/**
	 * Ajoute un nouveau bâtiment dans la liste des bâtiments à construire
	 * @param batiment type de bâtiment à construire
	 * @throws TimeFactorInconnu si le TimeFactor est inconnu pour le type demandé
	 */
	public void construire(Unite unite) throws TimeFactorInconnu
	{
		_constructions.add(new Construction(getIle(), unite));
	}

	/**
	 * @return file d'attente des construction, les temps restants ne prenne en compte que <b>l'état actuel</b> de l'ile
	 */
	public FileDAttente<Construction> getConstructions() {
		return _constructions;
	}
	
	public void incPop(final int nb) throws TimeFactorInconnu
	{
		setPop(pop() + nb);
	}
	
	public void decPop(final int nb) throws TimeFactorInconnu
	{
		setPop(Math.max(0, pop() - nb));
	}
	
	@Override
	// FIXME : créer un constructeur de recopie
	public String toString() {

		final StatsCombat statsCombat = getStatsCombat();
		final int att = statsCombat.attaque();
		final int def = statsCombat.defense();
		
		String cons;
		try
		{
			cons = _constructions.toString(getEtatMonde());
		} catch (Exception e)
		{
			cons = "[ERR]";
			e.printStackTrace();
		}
		
		return String.format("%s[%s/%s(+:%s);%s/%s] : %s : %s",
			_entite.getNom(),
			_pop,
			nourriture(),
			Temps.toString(_nextPop),
			att,
			def,
			Batiment.toString(_batiments),
			cons);
		
//		final StringBuilder sb = new StringBuilder();
//		
//		sb.append(_entite.getNom() + "[").append(_pop).append("/").append(nourriture()) // pop/nourriture
//			.append("(+:").append(Temps.toString(_nextPop)).append(");")
//			.append(att).append("/").append(def).append("] : ") // att/def
//			.append(_batiments).append(" : ") // batiment construits
//			.append(_constructions); // batiment en cours
//
//		return sb.toString();
	}

	@Override
	public Evenement prochainEvenement() throws Exception {
		
		final Construction prchConst = _constructions.peek();		
		
		// Evenements
		final Collection<Evenement> evenements = new Vector<Evenement>();
		
		// prochain habitant
		evenements.add(new NouvelHabitant(this, _nextPop));

		if (prchConst != null)
		{
			// Si la prochaine construction n'a pas été comencée
			// alors le prochain évènement sur les constructions sera une DEBUT de construction
			if (!prchConst.aEteCommencee())
			{
				final CommencerConstruction com = new CommencerConstruction(getEtatJoueur(), prchConst);
				if (com.estPossible(getEtatMonde()))
				{
					evenements.add(com);
				}
			}
			
			// Sinon le prochaine évènement sera un FIN de construction
			else
			{
				evenements.add(new TerminerConstruction(this, prchConst));
			}
		}
		
		return Evenement.prochainEvenementParmis(evenements);
	}

	public EtatJoueur getEtatJoueur() {
		return (EtatJoueur) _etatPere;
	}

	public Unite execute(final TerminerConstruction evenement) throws TimeFactorInconnu
	{
		final Construction construction = _constructions.poll();
		final Unite unite = construction._unite;
		
		// Conso de pop pour l'ile
		decPop(construction.getCoutPop());
		
		add(unite);
		
		// Recalcul de la nourriture
		nourriture(getProduction(true).nourriture());
		
		// Ajout des points pour le joueur
		getEtatJoueur().getScore().construire(unite);
		
		construction.terminer(getEtatJoueur().getTemps());
		
		// Historique des constructions du monde
		getEtatMonde().add(construction);
		
		return unite;
	}

	@Override
	public void execute(Evenement e)
	{
		// à implémenter pour le type d'évènement précis
	}

	public EtatMonde getEtatMonde()
	{
		return getEtatJoueur().getEtatMonde();
	}

	/**
	 * Calcul avec getTimeFactor
	 * @param newNourriture
	 * @return facteur de modification des temps de construction (voir {@link #getTimeFactor(double)})
	 * @throws TimeFactorInconnu 
	 */
	public void nourriture(final int newNourriture) throws TimeFactorInconnu {
		
		// Si la nourriture a changé, on doit changer le temps restant après le nextPop
		if (_nourriture != newNourriture)
		{
			final int oldNourriture = _nourriture;
			
			// On modifie la nourriture
			_nourriture = newNourriture;
			
			// 25 nov 2011 : Apparamment la pop s'incrémente si la nourriture a augmenté
			if (newNourriture > oldNourriture)
			{
				incPop(1);
			}
			
			// 25 nov 2011 : le nextPop est mis automatiquement à jour grace à incPop
//			_nextPop = Temps.applyTimeFactor(Temps.getTimeFactor(_nourriture, nourriture, _pop), _nextPop);
		}
	}

	/**
	 * <b>Attention:</b> Appeler après avoir ajouté tous les bâtiments
	 * @since 27 déc. 2013
	 */
	public void nourritureForXML(final Integer nourriture) throws TimeFactorInconnu
	{
		if (nourriture == null)
			_nourriture = getProduction(true).nourriture();
		else
			_nourriture = nourriture;
	}
	
	/**
	 * Calcul avec getTimeFactor
	 * @param newPop
	 * @throws TimeFactorInconnu
	 * @since 24 nov. 2011
	 */
	public void setPop(final int newPop) throws TimeFactorInconnu
	{
		// Si la nourriture a changé, on doit changer le temps restant après le nextPop
		if (_pop != newPop)
		{
			// On modifie tous les temps de construction
			for (final Construction construction : _constructions)
			{
				construction.applyTimeFactor(Temps.getTimeFactor(_pop, newPop));
			}
			
			// On modifie la pop
			_pop = newPop;
			
			// ainsi que le nextPop
			_nextPop = getInitialNextPop();
		}
	}
	
	/**
	 * @since 27 déc. 2013
	 */
	public void setPopForXML(final Integer pop) throws TimeFactorInconnu
	{
		if (pop == null)
			_pop = POP_INITIALE;
		else
			_pop = pop;
	}

//	/**
//	 * @param nourriture
//	 * @return facteur de modification des temps de construction (voir {@link #getTimeFactor(double)})
//	 */
//	public void nourriture(final int nourriture) {
//		
//		// Si la nourriture a changé, on doit changer le temps restant après le nextPop
//		if (_nourriture != nourriture)
//		{
//			// ancien nextPopInitial
//			final double oldInitialNextPop = getInitialNextPop();
//			
//			// On modifie la nourriture
//			_nourriture = nourriture;
//			
//			_nextPop = Temps.applyTimeFactor(oldInitialNextPop, getInitialNextPop(), _nextPop);
//		}
//	}
	
//	public void setPop(final int newPop) throws TimeFactorInconnu
//	{		
//		// Si la nourriture a changé, on doit changer le temps restant après le nextPop
//		if (_pop != newPop)
//		{
//			// On modifie la pop
//			_pop = newPop;
//			
//			// On modifie tous les temps de construction
//			for (final Construction construction : _constructions)
//			{
//				construction.recalculerTempsRestant();
//			}
//			
//			// ainsi que le nextPop
//			_nextPop = getInitialNextPop();
//		}
//	}

	public void add(final Unite unite) {
		
		// Batiment
		if (unite instanceof Batiment)
		{
			final Batiment batiment = (Batiment) unite;
			final int oldNb;
			if (!_batiments.containsKey(batiment))
			{
				oldNb = 0;
			}
			else
			{
				oldNb = _batiments.get(batiment);
			}
			_batiments.put(batiment, oldNb + 1);
		}
		else if (unite instanceof Vaisseau)
		{
			getEtatJoueur().add((Vaisseau) unite);
		}
	}
	
	public void add(int nb, final Unite unite)
	{
		for (int i = 0; i < nb; ++i)
		{
			add(unite);
		}
	}
	
	/**
	 * @return temps (initial) avant la prochaine pop 
	 * @since 9 nov. 2011
	 */
	private double getInitialNextPop() {
		
		final int nourriture = nourriture();
		final int pop = pop();
		
		if (nourriture == pop)
		{
			return Temps.INFINI;
		}
		else if (nourriture < pop)
		{
			// FIXME : si nourriture < pop => le temps restant sera la mort d'un habitant
			System.err.println("La mort d'habitant n'a pas encore été implémentée");
			return Temps.INFINI;
		}
		else
		{
			return (double) 10800 / (nourriture - pop);
		}
	}

	public int nourriture() {
		return _nourriture;
	}

	public int pop() {
		return _pop;
	}

	public Ile getIle() {
		return _entite;
	}

	public void execute(NouvelHabitant nouvelHabitant) throws TimeFactorInconnu {
		// On incrémente la pop
		incPop(1);
	}
	
	/**
	 * @return true si on peut continuer ou commencer les constructions
	 * @throws Exception 
	 * @since 10 nov. 2011
	 */
	public boolean peutConstruire() throws Exception
	{
		if (_constructions.isEmpty())
		{
			return false;
		}
		else
		{
			final Construction prchConst = _constructions.peek();
			if (prchConst.aEteCommencee())
			{
				final double tempsRestant = prchConst.getTempsRestant(getEtatMonde());
				return tempsRestant != Temps.INFINI && !Double.isInfinite(tempsRestant);
			}
			else
			{
				return getEtatJoueur().tempsPourCollecter(prchConst.getCout()) != Temps.INFINI;
			}
		}
	}
	
	@Override
	public void avancer(double temps) throws Exception
	{
		// Diminue le temps de construction
		_constructions.avancer(temps);
		
		// Diminue le temps pour la prochaine pop
		_nextPop = Temps.decrementer(_nextPop, temps);
	}

	/**
	 * Nombre de bâtiments, d'un certain type, prévus dans les constructions
	 * @param batiment bâtiment à compter
	 * @param constructionLimite construction servant de limite (exclue) dans le comptage
	 * @return
	 * @since 17 nov. 2011
	 */
	public int getNbPrevuAvant(Batiment batiment, final Progression constructionLimite) {
		int nb = 0;
		
		// On compte le nombre de bâtiment prévus avant la construction passée en argument 
		for (final Construction construction : _constructions)
		{
			// Construction limite trouvée
			if (construction == constructionLimite)
			{
				break;
			}
			else if (batiment == construction.getUnite())
			{
				++nb;
			}
		}
		
		return nb;
	}

	public int getPopMax()
	{
		return getNb(Batiment.CASERNE) * 2;
	}

	public boolean constructionPrevue(Unite unite)
	{
		for (final Construction construction : _constructions)
		{
			if (construction.getUnite() == unite)
			{
				return true;
			}
		}
		return false;
	}

	public boolean peutConstruire(Unite unite)
	{		
		return unite instanceof Batiment &&
			(
				   unite == CHAMPS
				|| unite == Batiment.ATELIER
				|| unite == Batiment.POMPE
				|| unite == Batiment.CASERNE
				|| unite == Batiment.TISSERAND
				|| unite == Batiment.PALAIS       && getNb(Batiment.PALAIS) == 0 // TODO : unique par joueur
		        || unite == Batiment.Carrière     && getNb(Batiment.Carrière) == 0
		        || unite == Batiment.TOUR_DE_GUET && getNb(Batiment.TOUR_DE_GUET) == 0
		        || unite == Batiment.École        && getNb(Batiment.École) == 0
		        || unite == Batiment.Université   && getNb(Batiment.Université) == 0
		        || unite == Batiment.MOULIN       && getNb(Batiment.MOULIN) == 0       && getNb(TISSERAND) > 0
		        || unite == Batiment.ARCHITECTE   && getNb(Batiment.ARCHITECTE) == 0   && getNb(École) > 0
				|| unite == Batiment.FONDERIE                                          && getNb(ATELIER) > 0   && getNb(Carrière) > 0
				|| unite == Batiment.FERME                                             && getNb(ATELIER) > 0   && getNb(CHAMPS) > 0
				
				// Poudre à canon
				|| (unite == Batiment.Fort || unite == Batiment.Canon || unite == Batiment.Archimortier) && getEtatJoueur().connait(Techno.Poudre_à_canon)
		    )
		    || unite instanceof Vaisseau &&
		    (
		    	   unite == Vaisseau.Apicopter && getNb(Batiment.ATELIER) > 0
		    	|| unite == Vaisseau.Drakkar   && getNb(Batiment.ATELIER) > 0 || getNb(Batiment.TISSERAND) > 0
						
				// Poudre à canon
				|| (unite == Vaisseau.Bombardier) && getEtatJoueur().connait(Techno.Poudre_à_canon)
		    )
		;
	}

	public int getPop()
	{
		return _pop;
	}
	
	public double getNextPop()
	{
		return _nextPop;
	}

	public void setNextPopForXML(String nextPop)
	{
		if (nextPop == null)
			_nextPop = getInitialNextPop();
		else
			_nextPop = Temps.parseDuree(nextPop);
	}
	
	public Set<Batiment> getBatiments()
	{
		return _batiments.keySet();
	}

	public void addConstructionForXML(Construction cons)
	{
		_constructions.add(cons);
	}

	public List<Techno> getTechnos()
	{
		return getEtatJoueur().getTechnos();
	}

	/**
	 * @param stats
	 */
	public void appliquerEffetsSurIle(final StatsCombat stats)
	{
		for (final Techno techno : getTechnos())
		{
			techno.appliquerEffetsSurIle(stats);
		}
		for (final Batiment bat : getBatiments())
		{
			bat.appliquerEffetsSurIle(stats);
		}
	}

	public void appliquerEffetsSurIle(Ressources res)
	{
		for (final Techno techno : getTechnos())
		{
			techno.appliquerEffetsSurIle(res);
		}
		for (final Batiment bat : getBatiments())
		{
			bat.appliquerEffetsSurIle(res);
		}
	}
}
